$(function() {

	var $menus = $("#menus"),
		$area = $("#area");
	var $body = $('body')

	// petri initial
	var petri = null;
	var viewer = null;

	/*state control*/
	var globalState = "idle";
	function stateCare(fn, state) {
		if (arguments.length == 1) {
			state = fn.care;
		}
		return function() {
			if (state instanceof RegExp && state.test(globalState)) {
				console.log('state test pass: ', globalState, '\n\texecuting function:', fn.name);
				return fn.apply(this, arguments);
			} else if (typeof state == 'string' && state == globalState) {
				console.log('state test pass: ', globalState, '\n\texecuting function:', fn.name);
				return fn.apply(this, arguments);
			}
			return;
		}
	}
	
	/*menu*/
	hideMenu.care = /menu$/;
	function showMenu(id, e) {
		chooseItem(e.target);
		$menus
		.data('lastCallEvent', e)
		.children().addClass('hidden')
		.end().find('#' + id).removeClass('hidden')
		.end().css({
			top: e.clientY + 'px',
			left: e.clientX + 'px'
		}).removeClass('hidden');
		globalState = id;
		e.preventDefault();
		e.stopPropagation();
		return false;
	}
	function hideMenu(e) {
		unchooseItem();
		$menus.addClass('hidden').data('object', null);
		globalState = 'idle';
		e.preventDefault();
		e.stopPropagation();
		return false;
	}

	

	/*movw item*/
	startMoving.state = 'moving';
	startMoving.care = 'idle';
	moving.care = startMoving.state;
	cancelMoving.care = startMoving.state;
	function startMoving(e) {
		moving.prev = e;
		chooseItem($(this));
		globalState = startMoving.state;
		e.preventDefault();
		return false;
	}
	function moving(e) {
		choosed.objects.forEach(function(obj) {
			viewer.move(obj, e.clientX - moving.prev.clientX, e.clientY - moving.prev.clientY);
			viewer.fresh(obj);
		})
		moving.prev = e;
		e.preventDefault();
		return false;
	}
	function cancelMoving(e) {
		unchooseItem();
		choosed.objects = null;
		moving.prev = null;
		globalState = 'idle';
	}
	moving.prev = null;

	/*choose item*/
	var choosed = {
		doms: null,
		objects: null
	}, $chooseBox = $("#chooseBox");
	chooseManyStart.care = 'idle';
	chooseManyStart.state = 'chooseMany';
	chooseManyMove.care = chooseManyStart.state;
	chooseManyDone.care = chooseManyStart.state;
	chooseManyDone.state = 'chooseManyDone';
	chooseManyCancel.care = chooseManyDone.state;

	function chooseItem(doms) {
		choosed.doms = $(doms).addClass('choose');
		choosed.objects = choosed.doms.map(function() {
			return $(this).data('object');
		}).toArray();
	}
	function unchooseItem() {
		choosed.doms.removeClass('choose');
		choosed.doms = null;
		choosed.objects = null;
	}
	function chooseManyStart(e) {
		if (!e.ctrlKey) {
			return;
		}
		$chooseBox
		.removeClass('hidden')
		.width(0).height(0)
		.css({
			top: e.clientY + 'px',
			left: e.clientX + 'px'
		})
		chooseManyStart.e = e;
		globalState = chooseManyStart.state;
	}
	function chooseManyCancel(e) {
		unchooseItem();
		globalState = 'idle';
	}

	function chooseManyDone(e) {
		var offset = $area.offset();
		var tl = $chooseBox.offset();
		var p1 = [tl.left - offset.left, tl.top - offset.top],
			p2 = [p1[0] + $chooseBox.width(), p1[1] + $chooseBox.height()];

		var $inside = $area.children('.place,.transition').filter(function() {
			var of = $(this).offset();
			return of.top >= p1[1] && of.top <= p2[1] && of.left >= p1[0] && of.left <= p2[0];
		})
		chooseItem(
			$inside
		)
		chooseManyStart.e = null;
		$chooseBox.addClass('hidden');
		globalState = $inside.length ? chooseManyDone.state : 'idle';
		e.preventDefault();
		e.stopPropagation();
		return false;
	}
	function chooseManyMove(e) {
		var oe = chooseManyStart.e,
			width = e.clientX - oe.clientX,
			height = e.clientY - oe.clientY;
		if (width < 0) {
			$chooseBox.width(-width).css('left', (oe.clientX + width) + 'px')
		} else {
			$chooseBox.width(width).css('left', oe.clientX + 'px')
		}
		if (height < 0) {
			$chooseBox.height(-height).css('top', (oe.clientY + height) + 'px')
		} else {
			$chooseBox.height(height).css('top', oe.clientY + 'px')
		}
	}
	chooseManyStart.e = null;

	/*connect*/
	connectStart.care = /(place|transition)\-menu/;
	connectMove.care = /connecting\-.*/;
	connectCancel.care =  /connecting\-.*/;

	function connectStart(e) {
		var require = $(this).data('require');
		$area.addClass('require-' + require);
		connectStart.e = e;
		connectStart.require = require;
		connectStart.from = $($menus.data('lastCallEvent').target);
		connectStart.connection = petri.connection.create();
		connectStart.dom = viewer.create(connectStart.connection);
		var offset = viewer.getPosition(connectStart.from.data('object'));
		viewer.move(connectStart.connection, offset[0], offset[1], 'from');
		viewer.move(connectStart.connection, offset[0], offset[1], 'to');
		hideMenu(e);
		globalState = 'connecting-' + require;
	}

	function connectMove(e) {
		var cur = viewer.getPosition(connectStart.connection).to, offset = $area.offset();
		viewer.move(connectStart.connection, e.clientX - offset.left - cur[0], e.clientY - offset.top - cur[1], 'to');
		viewer.freshPosition(connectStart.connection);
		connectStart.e = e;
	}

	function connectReset() {
		$area.removeClass('require-' + connectStart.require);
		globalState = 'idle';
		connectStart.require = null;
		connectStart.from = null;
		connectStart.connection = null;
		connectStart.dom = null;
	}

	function connectDone(e) {
		var $to = $(this), to = $to.data('object'),
			from = connectStart.from.data('object');
		connectStart.connection.connect(from, to);
		viewer.fresh(connectStart.connection);
		if (connectStart.connection.isRemoved) {
			// 重复连接
			var c = petri.connection.getLine(from, to);
			viewer.fresh(c);
		}
		to.type == 'transition' ? viewer.fresh(to) : null;
		connectReset();
	}

	function connectCancel(e) {
		viewer.remove(connectStart.connection);
		connectStart.connection.remove();
		connectReset();
		e.preventDefault();
		e.stopPropagation();
		return false;
	}
	
	// util function
	function rename(e) {
		var obj = $menus.data('object'),
			$input = $(this),
			n_name = $input.val();
		if (!n_name || !petri[obj.type].rename(obj.name, n_name)) {
			$input.val(obj.name);
			warning($input);
			return;
		}
		viewer.fresh(obj);
	}

	function removeObj(obj) {
		var connections = petri.connection.filter('from', obj);
		viewer.remove(obj);
		obj.remove();
		if (obj.type == 'place') {
			connections.forEach(function(c) {
				c.to && viewer.fresh(c.to);
			})
		}
	}

	function remove(e) {
		var obj = $menus.data('object');
		removeObj(obj);
	}

	function warning($input, time) {
		var i = 0;
		time = time || 3;
		var showing = setInterval(function() {
			$input.css('border-color', i % 2 ? '#f44336' : '');
			if (++i >= time * 2) {
				$input.css('border-color', '');
				clearInterval(showing);
			}
		}, 100);
		return showing;
	}

	// choose many item
	$area
	.on('mousedown', stateCare(chooseManyStart))
	.on('mousemove', stateCare(chooseManyMove))
	.on('mouseup', stateCare(chooseManyDone))
	.on('mousedown', stateCare(function(e) {
		if (!$(e.target).is('.choose')) {
			chooseManyCancel(e);
			return;
		} else {
			startMoving.call(choosed.doms, e);
		}
	}, chooseManyCancel.care))

	// move item
	$area
	.on('mousedown', '.place,.transition',
		stateCare(startMoving)
	)
	.on('mousemove',
		stateCare(moving)
	)
	.on('mouseup',
		stateCare(cancelMoving)
	)
	
	// menu caller
	$area
	.on('contextmenu',
		stateCare(function(e) {
			return showMenu('global-menu', e);
		}, 'idle')
	)
	.on('contextmenu',
		'.place',
		stateCare(function(e) {
			var p = $(this).data('object'),
				tokens = p.selfTokens();
			$menus
			.data('object', p)
			.find('#place-menu')
			.find('#place-name').val(p.name)
			.end()
			.find('#tokenArea').children().detach()
			.end().append(
				tokens.map(function(tk) {
					return viewer.get(tk);
				})
			)
			.end()
			.find('#token-count').text(tokens.length);

			return showMenu('place-menu', e);
		}, 'idle')
	)
	.on('contextmenu',
		'.transition',
		stateCare(function(e) {
			var tr = $(this).data('object');
			$menus
			.data('object', tr)
			.find('#transition-menu')
			.find('#transition-name').val(tr.name);
			return showMenu('transition-menu', e);
		}, 'idle')
	)
	.on('contextmenu',
		'.connection .menu',
		stateCare(function(e) {
			var c = $(this).parent().data('object');
			$menus
			.data('object', c)
			.find('#connection-menu')
			.find('#connection-name').val(c.name)
			.end()
			.find('#connection-count').val(c.count);
			return showMenu('connection-menu', e);
		}, 'idle')
	)

	// prevent menu auto close
	$menus
	.on('click', '.editable', stateCare(function(e) {
		e.stopPropagation();
	}, /(place|transition|connection)\-menu/))
	
	// create handler
	$menus
	.on('click', '.create', stateCare(function(e) {
		var type = $(this).data('type');
		var obj = petri[type].create();
		var $dom = viewer.create(obj);
		var offset = $area.offset();
		viewer.move(obj, e.clientX - offset.left, e.clientY - offset.top);
		viewer.fresh(obj);
	}, 'global-menu'))

	// remove handler
	$menus
	.on('click', '#remove-connection', stateCare(remove, 'connection-menu'))
	.on('click', '#remove-place', stateCare(remove, 'place-menu'))
	.on('click', '#remove-transition', stateCare(remove, 'transition-menu'))

	// rename handler
	$menus
	.on('change', '#place-name', stateCare(rename, 'place-menu'))
	.on('change', '#transition-name', stateCare(rename, 'transition-menu'))
	.on('change', '#connection-name', stateCare(rename, 'connection-menu'))

	// connect handler
	$menus
	.on('click', '.connect', stateCare(connectStart))
	$area
	.on('click', '.place', stateCare(connectDone, /connecting-place/))
	.on('click', '.transition', stateCare(connectDone, /connecting-transition/))
	.on('mousemove', stateCare(connectMove))

	// node-place handler
	$menus
	.on('click', '#add-token', stateCare(function(e) {
		var $place = $($menus.data('lastCallEvent').target),
			p = $place.data('object');
		var $tokens = $menus.find('#tokenArea').children();
		var tk = petri.token.create();
		tk.setIn(p);
		viewer.create(tk).appendTo($tokens.end());
		$menus.find('#token-count').text($tokens.length + 1);
		viewer.fresh(p);
	}, 'place-menu'))
	.on('click', '#sub-token', stateCare(function(e) {
		var $place = $($menus.data('lastCallEvent').target)
		var $tokens = $menus.find('#tokenArea').children();
		if (!$tokens.length) {
			warning($(this), 5);
			return;
		}
		var tk = $tokens.eq(0).data('object');
		viewer.remove(tk);
		tk.remove();
		$menus.find('#token-count').text($tokens.length - 1);
		viewer.fresh($place.data('object'));
	}, 'place-menu'))

	// node-connect handler
	$menus
	.on('change', '#connection-count', stateCare(function(e) {
		var $input = $(this), val = $input.val(), c = $menus.data('object');
		if (val <= 0) {
			$input.val(c.count);
			warning($input, 3);
			return;
		}
		if (c.count == val) {
			return;
		}
		c.count = val;
		viewer.fresh(c);
		if (c.to.type == 'transition') {
			viewer.fresh(c.to);
		}
	}, 'connection-menu'));

	// reset state
	$body
	.on('contextmenu', stateCare(hideMenu))
	.on('click', stateCare(hideMenu))
	.on('contextmenu', stateCare(connectCancel))

	// run petri
	var $run = $("#run"),
	$runAreaContainer = $("#runArea-container"),
	$runArea = $("#runArea"),
	$autoRunSpeed = $("#autoRunSpeed");
	callRunView.care = 'idle';
	callRunView.state = 'petri-running-idle';
	closeRunView.care = callRunView.state;
	autoRun.care = callRunView.state;
	autoRun.state = 'petri-running-busy';
	autoRunPause.care = autoRun.state;
	runBackward.care = callRunView.state;
	runForward.care = callRunView.state;
	runBackwardLeft.care = callRunView.state;
	runForwardRight.care = callRunView.state;
	runStep.care = callRunView.state;
	var runner = null, _petri = null, _petriView = null;
	function render(petri, viewer) {
		if (petri == _petri) {
			return;
		}
		_petri = petri;

		var _newView = new PetriNetView();
		_newView.cloneDomDataSet(viewer);
		_newView.init({
			petri: _petri,
			container: $runArea
		})

		_petriView && _petriView.clear();
		_petriView = _newView;

		_petriView.render();
	}
	function callRunView(e) {
		render(petri.clone(), viewer.freshDomDataSet());
		runner = new PetriNetExecutor(_petri, {
			onAutoRun: $.Callbacks().add(function(curPetri) {
				render(curPetri, _petriView);
			})
		});
		globalState = callRunView.state;
		$runAreaContainer.removeClass('hidden');
		$runAreaContainer.find('#control')
			.find('[data-opt]').removeClass('disabled').prop('disabled', false)
			.filter('[data-opt=pause]').addClass('disabled').prop('disabled', true);

	}
	function closeRunView(e) {
		_petriView.clear();
		$runAreaContainer.addClass('hidden');
		runner.stop();
		runner = null;
		_petri = null;
		_petriView = null;
		globalState = 'idle';
	}
	function autoRun(e) {
		// play
		$runAreaContainer.find('#control')
			.find('[data-opt]').addClass('disabled').prop('disabled', true)
			.filter('[data-opt=pause]').removeClass('disabled').prop('disabled', false);
		runner.option.delay = parseInt($autoRunSpeed.val());
		runner.autoRun();
		globalState = autoRun.state;
	}
	function autoRunPause(e) {
		// pause
		$runAreaContainer.find('#control')
			.find('[data-opt]').removeClass('disabled').prop('disabled', false)
			.filter('[data-opt=pause]').addClass('disabled').prop('disabled', true);
		runner.stop();
		globalState = callRunView.state;
	}
	function runBackward(e) {
		runner.backward(1);
		render(runner.getCurPetriNet(), _petriView);

	}
	function runForward(e) {
		runner.forward(1);
		render(runner.getCurPetriNet(), _petriView);

	}
	function runBackwardLeft(e) {
		runner.backward(runner.getLength());
		render(runner.getCurPetriNet(), _petriView);
	}
	function runForwardRight(e) {
		runner.forward(runner.getLength());
		render(runner.getCurPetriNet(), _petriView);
	}
	function runStep(e) {
		runner.fire();
		render(runner.getCurPetriNet(), _petriView);
	}


	$run
	.click(stateCare(callRunView));
	$runAreaContainer
	.on('click', '.close', stateCare(closeRunView))
	.on('click', '[data-opt=play]', stateCare(autoRun))
	.on('click', '[data-opt=pause]', stateCare(autoRunPause))
	.on('click', '[data-opt=backward]', stateCare(runBackward))
	.on('click', '[data-opt=forward]', stateCare(runForward))
	.on('click', '[data-opt=backward-left]', stateCare(runBackwardLeft))
	.on('click', '[data-opt=forward-right]', stateCare(runForwardRight))
	.on('click', '[data-opt=step]', stateCare(runStep));

	var $open = $("#open"), $save = $("#save"), $new = $("#new")
		$file = $("#file-get");

	var init = stateCare(function() {
		petri = new PetriNet();
		viewer && viewer.clear();
		viewer = new PetriNetView();
		viewer.init({
			petri: petri,
			container: $("#area")
		}).render();
		globalState = 'idle';
	}, 'idle');

	function download(fileName, content, blobOptions) {

        // blobOptions = {
        //     type: 'text/csv',
        //     endings: 'native' // or transparent
        // };
        blobOptions = blobOptions || {};

        var blob = new Blob([content], blobOptions);

        var $a = $("<a></a>").attr({
        	'href': URL.createObjectURL(blob),
        	download: fileName
        }).addClass('hidden').appendTo($body);
        globalState = 'downloading';
        setTimeout(function() {
        	$a[0].click();
        	$a.remove();
        	globalState = 'idle';
        })
    }
    function save(e) {
    	var obj = {
    		'PetriNet': petri.toJSON(),
    		'PetriNetView': viewer.freshDomDataSet().toJSON()
    	};
    	download(Date.now() + '.json', JSON.stringify(obj, null, 2), {
    		type: 'application/json'
    	})
    }
    save.care = 'idle';

    function readFile(file, callback) {
    	globalState = 'loading file';
    	var reader = new FileReader();
	    reader.readAsText(file);
	    reader.onload = function (e) {
	        //e.target  FileReader
	        //e.total   文件总大小，字节
	        //e.type    上传状态‘load’
	        //e.timeStamp  还不知道
	        var result = reader.result;
	        globalState = 'idle';
	        callback && callback(result);
	    }
    }

    function open(e) {
    	var file = this.files[0];
    	readFile(file, function(str) {
    		try {
    			var obj = JSON.parse(str);
    			init();
    			petri = PetriNet.parse(obj.PetriNet);
    			viewer = new PetriNetView();
    			viewer.init({
					petri: petri,
					container: $("#area")
				}).parse(obj.PetriNetView).render()
    		} catch(e) {
    			// throw e;
    			alert('加载文件' + file.name + '失败');
    			init();
    		}
    	})
    	this.value = '';
    }
    open.care = 'idle';

	$new.click(init);
	$save.click(stateCare(save));
	$file.change(stateCare(open));

	init();

	// 批量操作
	function deleteMany() {
		choosed.objects.forEach(function(obj) {
			removeObj(obj);
		})
		globalState = 'idle';
	}
	deleteMany.care = 'chooseManyDone';
	function option(e) {
		if (e.keyCode == 46) {
			// delete key
			stateCare(deleteMany).apply(this, arguments);
		}

	}
	option.care = 'idle';
	// keydown handler
	$body.on('keydown', option);

	var $info = $("#info"),
		$infoBody = $("#info-body").detach().removeClass('hidden'),
		autoFadeOut = null;
	$info
	.popover({
		html: true,
		content: function() {
			return $infoBody;
		}
	})
	.trigger('click');

})